package com.gitee.l0km.aocache;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.runtime.internal.AroundClosure;

import static com.gitee.l0km.aocache.SimpleLog.log;
import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.aocache.aop.AocacheAnnotatedAspect;
import com.gitee.l0km.aocache.config.AoCacheGlobalConfig;
import com.google.common.collect.ImmutableSet;

/**
 * 实现了 {@link InvocationHandler} 接口，用于动态代理处理 AspectJ 接口的调用。
 * 
 * <p>该类的主要功能包括：</p>
 * <ul>
 *     <li>代理 AspectJ 库的对象，实现指定的包名重定位接口。</li>
 *     <li>将参数和返回值的类型从原始类型转换为重定位类型。</li>
 *     <li>处理 Object 类的方法调用。</li>
 *     <li>支持 AroundClosure 类型参数的特殊处理。</li>
 * </ul>
 * 
 * <p>包名重定位：<br>
 * 即将 AspectJ 库中所有包名前缀是 {@code org.aspectj} 的类重定位为新的包名前缀 {@code com.gitee.l0km.aocache.aspectj}。<br>
 * 例如，{@code org.aspectj.lang.JoinPoint} 重定位为 {@code com.gitee.l0km.aocache.aspectj.lang.JoinPoint}。<br>
 * 该类编译后生成的字节码中，所有以 {@link #ORIGINAL_PACKAGE} 开头的类引用都被重定位到 {@link #RELOCATED_PACKAGE} 包下。<br>
 * 参见 {@code pom.xml} 中的 maven-shade-plugin 插件配置。</p>
 * 
 * <p>该类不会被公开引用，只会在 aspectj-maven-plugin 插件执行时通过 ASM 修改 .class 文件的方式植入。<br>
 * 植入位置为 .class 文件中所有调用 {@link AocacheAnnotatedAspect} 类的方法的代码，<br>
 * 将调用该类的方法的输入参数（如 {@link ProceedingJoinPoint} 或 {@link JoinPoint}）转换为重定位接口实例。</p>
 * 
 * <p>具体功能细节：</p>
 * <ul>
 *     <li><strong>代理对象包装：</strong> 通过 {@link #wrap(Object)} 方法，将实现了 AspectJ 接口的对象包装成代理对象，以便进行包名重定位。</li>
 *     <li><strong>参数和返回值转换：</strong> 在方法调用时，通过 {@link #wrapArgs(Object[])} 和 {@link #unwrapArgs(Object[])} 方法，将参数和返回值在原始类型和重定位类型之间进行转换。</li>
 *     <li><strong>方法调用处理：</strong> 通过 {@link #invoke(Object, Method, Object[])} 方法，处理代理对象的方法调用，并确保参数和返回值的正确转换。</li>
 *     <li><strong>AroundClosure 处理：</strong> 特别处理 {@link AroundClosure} 类型的参数，确保其在代理过程中正确转换和调用。</li>
 * </ul>
 */
public class AspecjrtDecorator implements InvocationHandler,AocacheConstant {
    private static final String ORIGINAL_PACKAGE = "org_aspectj".replace('_', '.');
    private static final String ORIGINAL_AROUND_COLSURE = ORIGINAL_PACKAGE + ".runtime.internal.AroundClosure";
    private static final String RELOCATED_PACKAGE = "com.gitee.l0km.aocache.aspectj";
	/** 代理对象的目标对象 */
    private final Object delegate;
	/**
	 * <p>是否开启调试输出的标志位。</p>
	 * <p>该标志位的值由 {@link AoCacheGlobalConfig#debugOutput} 和 {@link AoCacheGlobalConfig#debugOutputDetail} 共同决定，</p>
	 * <p>只有当这两个配置项都为 {@code true} 时，该标志位才为 {@code true}。</p>
	 */
	private final boolean debugOutput;

    private AspecjrtDecorator(Object delegate) {
        this.delegate = delegate;
        this.debugOutput = AoCacheGlobalConfig.debugOutput && AoCacheGlobalConfig.debugOutputDetail;
	}

	/**
	 * [TEST ONLY] 获取当前对象的委托实例。
	 *
	 * @return 当前委托对象
	 */
	public Object getDelegate() {
		return delegate;
	}

	/**
     * 该方法用于处理代理对象的方法调用。
     * 如果调用的是对象方法，则调用相应的处理方法。
     * 否则，解析原始方法并调用目标对象的方法，传递解包后的参数。
     * 最后，将结果进行包装后返回。
     *
     * @param proxy  代理对象
     * @param method 被调用的方法
     * @param args   方法参数
     * @return 方法调用的结果
     * @throws Throwable 可能抛出的异常
     */
    @Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	if(debugOutput) {
    		log("method {}", method);
    	}
		if (isObjectMethod(method)) {
			return handleObjectMethods(method, args);
		}

		Method originalMethod = resolveOriginalMethod(delegate.getClass(), method);
		originalMethod.setAccessible(true);
		if(debugOutput) {
			log("originalMethod {}", originalMethod);
		}
		Object[] unwrappedArgs = unwrapArgs(args);
		Object result = originalMethod.invoke(delegate, unwrappedArgs);

		return wrap(result);
	}
    private Object handleObjectMethods(Method method, Object[] args) throws Throwable {
	    // 直接调用目标对象的方法
	    Method delegateMethod = Object.class.getDeclaredMethod(
	        method.getName(), 
	        method.getParameterTypes()
	    );
	    delegateMethod.setAccessible(true);
	    
		return delegateMethod.invoke(delegate, args);
	}

	/**
	 * 将 AspectJ 原生对象包装为对应的包名重位向对象。<br>
     * 如果输入对象是 AspectJ 原生对象，则返回一个对应的包名重定位的接口代理实例或继承对象代理实例（AroundClosure）。否则回原对象。
	 * 
	 * @param obj 要包装的对象，如果为 null 则返回 null。
	 * @return 如果对象不具有 AspectJ 接口，则返回原对象；否则返回一个代理实例。
	 */
	public static Object wrap(Object obj) {
		if (obj == null) {
			return null;
		}
		// 优先处理AroundClosure类型
		if (isOriginalAroundClosure(obj.getClass())) {
			return wrapAroundClosure(obj);
		}
	    Class<?>[] interfaces = getRelocatedInterfaces(obj.getClass());
	    return interfaces.length > 0 ? Proxy.newProxyInstance(
	        obj.getClass().getClassLoader(),
	        interfaces,
	        new AspecjrtDecorator(obj)
	    ) : obj;
	}

    /**
	 * 解包被代理对象，还原原始AspectJ实例
	 * @param obj 可能被包装过的对象
	 * @return 原始对象或输入对象本身
	 */
	private static Object unwrap(Object obj) {
	    if (obj == null) {
	    	return null;
	    }
	    
	    if (obj instanceof RelocatedAroundClosure) {
	    	return ((RelocatedAroundClosure) obj).delegate;
	    }
	    
	    if (Proxy.isProxyClass(obj.getClass())) {
	        InvocationHandler handler = Proxy.getInvocationHandler(obj);
	        if (handler instanceof AspecjrtDecorator) {
	            return ((AspecjrtDecorator) handler).delegate; // 获取原始目标
	        }
	    }
	    return obj;
	}

	/**
	 * 转换给定的参数数组，根据 wrap 标志决定是包装还是解包参数。
	 *
	 * @param args 需要转换的参数数组，可以为 null。
	 * @param wrap 如果为 true，则对参数进行包装；如果为 false，则对参数进行解包。
	 * @return 转换后的参数数组，如果输入为 null，则返回 null；如果输入为空数组，则返回 EMPTY_ARGS。
	 */
	private static Object[] tranform(Object[] args, boolean wrap) {
	    if (args == null) {
			return null;
		}
		if (args.length == 0) {
			return EMPTY_OBJECT_ARRAY;
		}
		Object[] transformed = new Object[args.length];
	    if(wrap) {
		for (int i = 0; i < args.length; i++) {
				transformed[i] = wrap(args[i]);
	        }
	    }else {
	        for (int i = 0; i < args.length; i++) {
	            transformed[i] = unwrap(args[i]);
	        }
	    }
		return transformed;
	}

	/**
	 * 包装方法参数，将特定类型的参数进行处理。
	 * 
	 * @param args 原始参数数组，可能为 null。
	 * @return 包装后的参数数组，如果输入为 null，则返回 null。
	 * @see #transform(Object[], boolean)
	 */
    private static Object[] wrapArgs(Object[] args) {
		return tranform(args, true);
	}

    /**
     * 解包给定的参数数组。
     * 
     * @param args 需要解包的参数数组，可以为 null。
     * @return 解包后的参数数组，如果输入为 null，则返回 null；如果输入为空数组，则返回一个空的参数数组。
     * @see #transform(Object[], boolean)
     */
    private static Object[] unwrapArgs(Object[] args) {
		return tranform(args, false);
	}
	private static final Set<String> OBJECT_METHOD_NAMES = ImmutableSet.copyOf(Arrays.asList(
	    "equals", "hashCode", "toString", "getClass", "notify", "notifyAll", "wait", "finalize", "clone"
	));
	/**
	 * 检查给定的方法是否为 Object 类的方法。
	 *
	 * @param method 要检查的方法
	 * @return 如果方法是 Object 类的方法，则返回 true；否则返回 false
	 */
	private static boolean isObjectMethod(Method method) {
		if (OBJECT_METHOD_NAMES.contains(method.getName())) {
			for (Method objectMethod : Object.class.getDeclaredMethods()) {
				if (method.getName().equals(objectMethod.getName())
						&& Arrays.equals(method.getParameterTypes(), objectMethod.getParameterTypes())
						&& method.getReturnType().equals(objectMethod.getReturnType())) {
					return true;
				}
			}
		}
		return false;
	}

    /**
     * 检查给定的类是否为原始的 AroundClosure 类。
     * 
     * @param clazz 要检查的类
     * @return 如果 clazz 是 org.aspectj.runtime.internal.AroundClosure 的子类，则返回
     *         true；否则返回 false。
     */
    private static boolean isOriginalAroundClosure(Class<?> clazz) {
		while (clazz != null) {
			if (ORIGINAL_AROUND_COLSURE.equals(clazz.getName())) {
				return true;
			}
			clazz = clazz.getSuperclass();
		}
		return false;
	}

	/**
	 * 获取指定类的重定位接口列表。
	 * 
	 * 此方法会检查给定类的所有接口，如果接口的名称以原始包名开头，
	 * 则将其替换为重定位包名，并尝试加载重定位后的接口类。
	 * 
	 * @param clazz 要检查的类
	 * @return 重定位后的接口类数组
	 */
    @AoWeakCacheable(maximumSize = 200, expireAfterAccess = 240, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
	private static Class<?>[] getRelocatedInterfaces(Class<?> clazz) {
	    Class<?>[] interfaces = clazz.getInterfaces();
	    List<Class<?>> relocated = new ArrayList<>();
	    for (Class<?> iface : interfaces) {
	        String originalName = iface.getName();
	        if (originalName.startsWith(ORIGINAL_PACKAGE)) {
	            String relocatedName = originalName.replace(
	                ORIGINAL_PACKAGE, 
	                RELOCATED_PACKAGE
	            );
	            try {
	                relocated.add(Class.forName(
	                    relocatedName, 
	                    true, 
	                    AspecjrtDecorator.class.getClassLoader()
	                ));
	            } catch (ClassNotFoundException e) {
	                throw new RuntimeException(e);
	            }
	        }
	    }
	    return relocated.toArray(new Class<?>[0]);
	}
    /**
     * 解析重定位后的类型获取原始类型
     * 
     * @param relocatedType 可能被包名重定位后的类对象
     * @param classLoader 用于加载原始类的类加载器
     * @return 解析后的原始类对象（当属于重定位包时），否则返回原类对象
     */
    private static Class<?> resolveOriginalType(Class<?> relocatedType, ClassLoader classLoader) {
        String typeName = relocatedType.getName();
        if (typeName.startsWith(RELOCATED_PACKAGE)) {
            String originalName = typeName.replace(RELOCATED_PACKAGE, ORIGINAL_PACKAGE);
            try {
                return Class.forName(originalName, true, classLoader);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return relocatedType;
    }
	/**
	 * 解析指定方法的原始参数类型
	 * @param delegateType
	 * @param method
	 * @return 原始参数类型数组
	 * @see #resolveOriginalType(Class, ClassLoader)
	 */
	private static Class<?>[] resolveOriginalParamTypes(Class<?> delegateType, Method method) {
		Class<?>[] originalParamTypes = new Class[method.getParameterTypes().length];
		for (int i = 0; i < originalParamTypes.length; i++) {
			originalParamTypes[i] = resolveOriginalType(method.getParameterTypes()[i], delegateType.getClassLoader());
		}
		return originalParamTypes;
	}
    /**
     * 从委托类解析指定方法的原始方法
     * 通过代理方法的名称和原始参数类型（可能经过类型解析转换），在委托类中精确查找对应的原始方法。
     * 使用反射机制获取方法，若查找失败会将NoSuchMethodException转换为运行时异常抛出。
     * 
     * @param delegateType 委托类型，需要从中查找原始方法的类对象
     * @param method 代理方法对象，包含需要匹配的方法名和参数类型信息
     * @return 在委托类中找到的与代理方法匹配的原始方法
     * @throws RuntimeException 当无法在委托类中找到匹配的方法时抛出
     * @see #resolveOriginalParamTypes(Class, Method)
     */
	@AoWeakCacheable(maximumSize = 1000, expireAfterAccess = 240, expireAfterAccessTimeUnit = TimeUnit.SECONDS)
    private static Method resolveOriginalMethod(Class<?> delegateType, Method method) {
        Class<?>[] originalParamTypes = resolveOriginalParamTypes(delegateType, method);
        try {
            return delegateType.getMethod(method.getName(), originalParamTypes);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 包装给定的目标对象为 RelocatedAroundClosure 实例。
     * 如果目标对象已经是 RelocatedAroundClosure 的实例，则直接返回该实例。
     * 否则，创建并返回一个新的 RelocatedAroundClosure 实例。
     * 
     * 不直接创建 RelocatedAroundClosure 的实例，而是通过包装原始的 AroundClosure 实例来实现。避免重复的创建和销毁。
     *
     * @param delegate 需要包装的目标对象
     * @return 包装后的 RelocatedAroundClosure 实例
     */
    private static RelocatedAroundClosure wrapAroundClosure(Object delegate) {
        if (delegate instanceof RelocatedAroundClosure) {
            return (RelocatedAroundClosure) delegate;
        }
        return new RelocatedAroundClosure(delegate);
    }
	
    private static class RelocatedAroundClosure extends AroundClosure {

        /**
         * 代理的原生包名的{@link AroundClosure}实例
         */
        final Object delegate;

		private RelocatedAroundClosure(Object delegate) {
			/** 将原始的参数数组包装为包装后的数组  */
			super(wrapArgs((Object[]) invokeDelegateMethod(delegate, "getState")));
			this.delegate = delegate;
		}

		@Override
		public Object run(Object[] args) throws Throwable {
            /** run方法的执行环境为没有重定位的环境，因此需要将包装后的参数数组还原为原始的参数数组 */
			return wrap(invokeDelegateMethod(delegate, "run", new Class[] { Object[].class }, unwrapArgs(args)));
		}

		@Override
		public int getFlags() {
			return (Integer) invokeDelegateMethod(delegate, "getFlags");
		}

		@Override
		public Object[] getState() {
			return wrapArgs((Object[]) invokeDelegateMethod(delegate, "getState"));
		}

		@Override
		public Object[] getPreInitializationState() {
			return wrapArgs((Object[]) invokeDelegateMethod(delegate, "getPreInitializationState"));
		}

		@Override
		public ProceedingJoinPoint linkClosureAndJoinPoint() {
			return (ProceedingJoinPoint) wrap(invokeDelegateMethod(delegate, "linkClosureAndJoinPoint"));
		}

		@Override
		public ProceedingJoinPoint linkClosureAndJoinPoint(int flags) {
			Object value = invokeDelegateMethod(delegate, "linkClosureAndJoinPoint", new Class[] { int.class },
					new Object[] { flags });
			return (ProceedingJoinPoint) wrap(value);
		}

//		@Override
//		public String toString() {
//			return (String) invokeDelegateMethod(delegate, "toString", EMPTY_CLASS_PARAMETERS, EMPTY_OBJECT_ARRAY);
//		}
//
//		@Override
//		public boolean equals(Object obj) {
//			if (obj instanceof RelocatedAroundClosure) {
//				return (boolean) invokeDelegateMethod(delegate, "equals",
//						new Class[] { Object.class }, new Object[] { ((RelocatedAroundClosure) obj).delegate });
//			}
//			return false;
//		}
//
//		@Override
//		public int hashCode() {
//			return (Integer) invokeDelegateMethod(delegate, "hashCode", EMPTY_CLASS_PARAMETERS, EMPTY_OBJECT_ARRAY);
//		}

		/** @see #invokeDelegateMethod(Object, String, Class[], Object[]) */
        private static Object invokeDelegateMethod(Object delegate, String methodName) {
			return invokeDelegateMethod(delegate, methodName, EMPTY_CLASS_PARAMETERS, EMPTY_OBJECT_ARRAY);
        }

        /**
         * 通过反射调用指定对象的目标方法。
         *
         * @param delegate     目标对象
         * @param methodName 方法名称
         * @param paramTypes 方法参数类型数组
         * @param args       方法参数值数组
         * @return 方法返回值
         * @throws RuntimeException 如果方法不存在、无法访问或调用过程中发生异常
         */
        private static Object invokeDelegateMethod(Object delegate, String methodName, Class<?>[] paramTypes,
                Object[] args) {
            try {
				Method method = delegate.getClass().getMethod(methodName, paramTypes);
				method.setAccessible(true);
				return method.invoke(delegate, args);
			} catch (NoSuchMethodException | IllegalAccessException e) {
				throw new RuntimeException("Failed to invoke " + methodName, e);
			} catch (InvocationTargetException e) {
				// 处理 InvocationTargetException，获取原始异常
				Throwable cause = e.getCause();
				if (cause instanceof RuntimeException) {
					throw (RuntimeException) cause;
				} else {
					throw new RuntimeException("Failed to invoke " + methodName, cause);
				}
			}
		}
	}
}
